Introduction
Welcome!
Code Examples
Day 1
Challenge: KISS
Solution: KISS
Day 2
Challenge: Type Annotations
Solution: Type Annotations
Day 3
Challenge: Decoupling
Solution: Decoupling
Day 4
Challenge: DRY
Solution: DRY
Day 5
Challenge: String Formatting
Solution: String Formatting
Day 6
Challenge: Law Of Demeter
Solution: Law Of Demeter
Day 7
Challenge: Better Discounts
Solution: Better Discounts
Day 8
Challenge: Payment Strategy
Solution: Payment Strategy
Day 9
Challenge: Plugins
Solution: Plugins
Day 10
Challenge: Object Oriented To Functional
Solution: Object Oriented To Functional
Day 11
Challenge: Cohesion
Solution: Cohesion
Day 12
Challenge: MVP
Solution: MVP
Day 13
Challenge: Inheritance
Solution: Inheritance
Day 14
Challenge: Abstraction
Solution: Abstraction
Day 15
Challenge: Higher-Order Functions
Solution: Higher-Order Functions
Day 16
Challenge: Configuration
Solution: Configuration
Day 17
Challenge: Concurrency
Solution: Concurrency
Day 18
Challenge: Refactoring
Solution: Refactoring
Day 19
Challenge: Itertools
Solution: Itertools
Day 20
Challenge: Inappropriate Intimacy
Solution: Inappropriate Intimacy
Wrap Up
End of Part 1

Hi Andreas, hope you're doing well!
Here is my solution:
*********** Code *************
from typing import Any, Callable
import json
from pathlib import Path
from functools import partial
import requests
HttpGet = Callable[[str], Any]
class ConfigFileMissing(Exception):
pass
class CityNotFoundError(Exception):
pass
def get(url: str) -> Any:
response = requests.get(url, timeout=5)
response.raise_for_status() # Raise an exception if the request failed
return response.json()
def get_forecast(http_get: HttpGet, url: str, api_key: str, city: str) -> dict[str, Any]:
url = url.format(city, api_key)
response = http_get(url)
if "main" not in response:
raise CityNotFoundError(
f"Couldn't find weather data. Check '{city}' if it exists and is correctly spelled.\n"
)
return response
def get_temperature(full_weather_forecast: dict[str, Any]) -> float:
temperature = full_weather_forecast["main"]["temp"]
return temperature - 273.15 # convert from Kelvin to Celsius
def get_humidity(full_weather_forecast: dict[str, Any]) -> int:
return full_weather_forecast["main"]["humidity"]
def get_wind_speed(full_weather_forecast: dict[str, Any]) -> float:
return full_weather_forecast["wind"]["speed"]
def get_wind_direction(full_weather_forecast: dict[str, Any]) -> int:
return full_weather_forecast["wind"]["deg"]
def main() -> None:
# Loading config object from json file
config_file_path = Path(".").absolute() / "config.json"
if not config_file_path.is_file():
raise ConfigFileMissing("Please check if config.json file is present.")
config = json.loads(config_file_path.read_text())
get_weather = partial(get_forecast, get, config["url"], config["api_key"])
city = config["city"]
weather_forecast = get_weather(city)
print(
f"The current temperature in {city} is {get_temperature(weather_forecast):.1f} °C."
)
print(f"The current humidity in {city} is {get_humidity(weather_forecast)}%.")
print(
f"The current wind speed in {city} is {get_wind_speed(weather_forecast) } m/s from direction {get_wind_direction(weather_forecast)} degrees."
)
if __name__ == "__main__":
main()
*********** EndCode *************
*********** config.json ***********
{
"url": "http://api.openweathermap.org/data/2.5/weather?q={}&appid={}",
"api_key": "123456",
"city": "Utrecht"
}
*********** End config.json *********
I could move api key to an .env file but to keep it simple in the exercise I just decided to keep it inside the json file.
Have a good day!
Alberto.
Fair enough! As long as you are conscious about that decision. How do you like the challenges? :)
Hi Andreas, yes, I really like the way we use the code version from the previous challenge in the next one to enhance functionality or apply a different design pattern.
Glad to hear that Alberto! Feedback like that is precious for us. Could you please fill out the following form? It helps us improve the courses! :)
Since this code can work with several APIs, I also created the json file to setup several APIs. Here the code:
from typing import Any, Callable
from functools import partial
import requests
import json
API_NAME = "openweathermap"
CONFIG_FILE = "challenge_configurations/config/config.json"
HttpGet = Callable[[str], Any]
class CityNotFoundError(Exception):
pass
def get(url: str) -> Any:
response = requests.get(url, timeout=5)
response.raise_for_status() # Raise an exception if the request failed
return response.json()
def get_forecast(http_get: HttpGet, api_key: str, city: str, url: str) -> dict[str, Any]:
url = f"{url}?q={city}&appid={api_key}"
response = http_get(url)
if "main" not in response:
raise CityNotFoundError(
f"Couldn't find weather data. Check '{city}' if it exists and is correctly spelled.\n"
)
return response
def get_temperature(full_weather_forecast: dict[str, Any]) -> float:
temperature = full_weather_forecast["main"]["temp"]
return temperature - 273.15 # convert from Kelvin to Celsius
def get_humidity(full_weather_forecast: dict[str, Any]) -> int:
return full_weather_forecast["main"]["humidity"]
def get_wind_speed(full_weather_forecast: dict[str, Any]) -> float:
return full_weather_forecast["wind"]["speed"]
def get_wind_direction(full_weather_forecast: dict[str, Any]) -> int:
return full_weather_forecast["wind"]["deg"]
def main() -> None:
url:str = ''
api_key:str = ''
with open(CONFIG_FILE, "r") as f:
data = json.load(f)
for config in data['config_datails']:
if config['api_name'] == API_NAME:
api_key = config['api_key']
url = config['url']
break
else:
print('Invalid api name')
return
get_weather = partial(get_forecast, get, api_key)
city = "New York"
weather_forecast = get_weather(city, url)
print(
f"The current temperature in {city} is {get_temperature(weather_forecast):.1f} °C."
)
print(f"The current humidity in {city} is {get_humidity(weather_forecast)}%.")
print(
f"The current wind speed in {city} is {get_wind_speed(weather_forecast) } m/s from direction {get_wind_direction(weather_forecast)} degrees."
)
if __name__ == "__main__":
main()
Nice solution! Just one question, how is the config_file setup?
This is the config.json file:
{
"config_datails": [
{
"api_name":"openweathermap",
"api_key":"767ee9774dccbca580fdd42f0c9a2c1c",
"url":"http://api.openweathermap.org/data/2.5/weather"
},
{
"api_name":"google_maps",
"api_key":"my_key_value",
"url":"https://googlemap.com/weather"
}
]
}
Ok! I however would be careful storing keys in a json file, it is recommeded to use enviromental variables or a
.envfile. This is so we do not accidentally commit and publish is somewhere like GitHub.Furthermore, if that is your API key for openweathermap, I would recommend you to remove that key immediately and use a new one
Yes, I agree with you Andreas. The API was deleted and a new one created.
Thanks
Good! No worries!
Hi Arjan,
I like that solution specifing a url_template_string including a placeholder for the city.
As you mentioned at 0:40 I would prefer to store the API key in the .env to avoid an accidentially upload of the api_key to github.
It is already mentioned in the comments below, that pydantic will deprecate some methods like parse_raw, thats why I try to create some generic functions to save and load pydantic_based jsonfiles :-):
def save_pydantic_model(file_path: str, pydantic_model: BaseModel) -> None:
with open(file_path, "w") as file:
file.write(pydantic_model.model_dump_json())
T = TypeVar("T", bound=BaseModel)
def load_pydantic_model(file_path: str, pydantic_model: Type[T]) -> T:
with open(file_path, "r") as file:
return pydantic_model.model_validate_json(file.read())
There are more benefits as well to storing the API key as an environment variable because it isolates critical application configuration and lets other services access the variables as well (as long as they are on the same machine of course).
Nice solution! I would consider using the new version of generics also to make the code a bit more sleek! We released a Tuesday Tip about it not long ago: Python 3.12 Generics in a Nutshell
Hi Andreas,
thanks for the feedback and your hint regarding Generics in 3.12.
Unfortunately I have to stay in 3.11 in my daily work because of a package a package being not valid for 3.12 :-(.
That's why I am happy that your challenges specify 3.11 in the .toml :-D so I can train the "old" handling of generics.
PS: Regarding your feedback for "Day_13" of course def main(): is missing, thanks for the hint.
I understand, hopefully your daily work gets upgraded to 3.12 :D
I used python-decouple to read from configuration files (.env) and for the rest, my solution is not as clean as the proposed one is:
rom typing import Any, Callable
from functools import partial
import json
import requests
CONFIG_FILE = "config.json"
with open(CONFIG_FILE, encoding="UTF_8") as f:
config = json.load(f)
HttpGet = Callable[[str], Any]
class CityNotFoundError(Exception):
pass
def get(url: str) -> Any:
response = requests.get(url, timeout=5)
response.raise_for_status() # Raise an exception if the request failed
return response.json()
def get_forecast(http_get: HttpGet, api_key: str, city: str) -> dict[str, Any]:
api_url = config["API_URL"]
url = f"{api_url}?q={city}&appid={api_key}"
response = http_get(url)
if "main" not in response:
raise CityNotFoundError(
f"Couldn't find weather data. Check '{city}' if it exists and is correctly spelled.\n"
)
return response
def get_temperature(full_weather_forecast: dict[str, Any]) -> float:
temperature = full_weather_forecast["main"]["temp"]
return temperature - 273.15 # convert from Kelvin to Celsius
def get_humidity(full_weather_forecast: dict[str, Any]) -> int:
return full_weather_forecast["main"]["humidity"]
def get_wind_speed(full_weather_forecast: dict[str, Any]) -> float:
return full_weather_forecast["wind"]["speed"]
def get_wind_direction(full_weather_forecast: dict[str, Any]) -> int:
return full_weather_forecast["wind"]["deg"]
def main() -> None:
api_key = config["API_KEY"]
for city in config["cities"]:
get_weather = partial(get_forecast, get, api_key)
weather_forecast = get_weather(city)
temperature = get_temperature(weather_forecast)
humidity = get_humidity(weather_forecast)
wind_speed = get_wind_speed(weather_forecast)
wind_direction = get_wind_direction(weather_forecast)
print(f"The current temperature in {city} is {temperature:.1f} °C.")
print(f"The current humidity in {city} is {humidity}%.")
print(
f"The current wind speed in {city} is {wind_speed} m/s from direction {wind_direction} degrees."
)
if __name__ == "__main__":
main()
I'm a big fan of dotenv, nice to see a json alternative
from dotenv import load_dotenv
# Load all envronment variables
load_dotenv()
API_KEY: str = os.getenv("API_KEY", "UnKnown")
Me too - I do that all the time. I wanted to keep the exercises simple and standalone, so I haven't used dotenv a lot in my courses.